import { DictArray } from "../utils/array.util"
import ObservableFacade from "./ObservableFacade"
import ObservableSubscribeMessage from "./ObservableSubscribeMessage"
import ObservableUnsubscribeMessage from "./ObservableUnsubscribeMessage"
import ObservationObserverAdaptor from "./ObservationObserverAdaptor"

export default class ObservableRegistration {
  messenger: any
  dataCache: {}
  subscribedObservers: any
  observersToRemove: any
  objectFactories: {}
  observableToFactoryMap: {}
  observableObserver: any
  constructor(t) {
    this.messenger = t
    this.dataCache = {}
    this.subscribedObservers = new DictArray()
    this.observersToRemove = new DictArray()
    this.objectFactories = {}
    this.observableToFactoryMap = {}
    const e = new ObservableFacade(this)
    this.observableObserver = new ObservationObserverAdaptor(e)
    this.messenger.addObserver(this.observableObserver)
  }
  dispose() {
    for (const t of this.subscribedObservers.keys) this.messenger.send(new ObservableUnsubscribeMessage(t))
    this.messenger.removeObserver(this.observableObserver)
  }
  register(t, e) {
    const s = this
    if (e) {
      const s = this.objectFactories[e]
      if (!s) throw new Error(`${e} factory was not provided for ${t}`)
      this.observableToFactoryMap[t] = s
    }
    return new (class Observable {
      subscribe(e) {
        return s.subscribe(t, e)
      }
      waitUntil(e) {
        return s.waitUntil(t, e)
      }
    })()
  }
  initObjectFactories(t, ...e) {
    for (const s of e) {
      const e = new s(t)
      this.objectFactories[e.observableKey] = e
    }
  }
  subscribe(t, e) {
    if ("function" == typeof e) return this.subscribeWithCallback(t, e)
    if (e.onChanged && "function" == typeof e.onChanged) return this.subscribeWithObserver(t, e)
    throw Error("The argument provided to subscribe was not a valid observer\nExpected a function or an object with an `onChanged` function")
  }
  subscribeWithCallback(t, e) {
    return this.addObserver(
      t,
      new (class CallbackAdaptor {
        onChanged(t) {
          e(t)
        }
      })()
    )
  }
  subscribeWithObserver(t, e) {
    return this.addObserver(
      t,
      new (class ObserverAdaptor {
        onChanged(t) {
          e.onChanged(t)
        }
      })()
    )
  }
  waitUntil(t, e) {
    if ("function" == typeof e) return this.waitWithCallback(t, e)
    if (e.hasOwnProperty("waitUntil") && "function" == typeof e.waitUntil) return this.waitWithWaiter(t, e)
    throw Error("The argument provided to waitUntil was not a valid waiter\nExpected a function or an object with an `waitUntil` function")
  }
  waitWithCallback(t, e) {
    class WaiterCallbackAdaptor {
      obsIntfBuilder: any
      res: any
      constructor(t, e) {
        ;(this.obsIntfBuilder = t), (this.res = e)
      }
      onChanged(s) {
        e(s) && (this.res(), this.obsIntfBuilder.observersToRemove.add(t, this))
      }
    }
    return new Promise(e => {
      this.addObserver(t, new WaiterCallbackAdaptor(this, e))
    })
  }
  waitWithWaiter(t, e) {
    class WaiterAdaptor {
      obsIntfBuilder: any
      res: any
      constructor(t, e) {
        ;(this.obsIntfBuilder = t), (this.res = e)
      }
      onChanged(s) {
        e.waitUntil(s) && (this.res(), this.obsIntfBuilder.observersToRemove.add(t, this))
      }
    }
    return new Promise(e => {
      this.addObserver(t, new WaiterAdaptor(this, e))
    })
  }
  observe(t) {
    const { observableKey: e, observableData: s } = t,
      i = this.observableToFactoryMap[e]
    this.dataCache[t.observableKey] = i ? i.create(s, this.dataCache[t.observableKey]) : s
    const r = this.subscribedObservers.getValuesAtKey(e)
    if (r) for (const e of r) e.onChanged(this.dataCache[t.observableKey])
    this.removeDeadObservers(e)
  }
  addObserver(t, e) {
    0 === this.subscribedObservers.valuesPerKey(t) && this.messenger.send(new ObservableSubscribeMessage(t)), this.subscribedObservers.add(t, e)
    const s = this.dataCache[t]
    s && (e.onChanged(s), this.removeDeadObservers(t))
    const i = this
    return new (class Cancellable {
      cancel() {
        i.removeObserver(t, e)
      }
    })()
  }
  removeObserver(t, e) {
    this.subscribedObservers.remove(t, e), 0 === this.subscribedObservers.valuesPerKey(t) && this.messenger.send(new ObservableUnsubscribeMessage(t))
  }
  removeDeadObservers(t) {
    const e = this.observersToRemove.getValuesAtKey(t)
    for (const s of e) this.removeObserver(t, s), this.observersToRemove.remove(t, s)
  }
}
